/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package avl.sv.shared.XML;

import avl.sv.shared.Annotations.Annotation;
import avl.sv.shared.Annotations.AnnotationSet;
import avl.sv.shared.ClassifierClass;
import avl.sv.shared.Solution;
import avl.sv.shared.SlideReference;
import avl.sv.shared.model.classifier.AbstractClassifier;
import avl.sv.shared.model.classifier.ClassifierSVM;
import avl.sv.shared.model.featureGenerator.AbstractFeatureGenerator;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.DatatypeConverter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import libsvm.svm_parameter;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

/**
 *
 * @author benbryan
 */
public class SolutionXML_Parser {

    private ClassifierClass classifierClassTemp;
    private Solution solution;
    private AnnotationSet annotationSetTemp;

//    public static void main(String args[]) {
//        SolutionXML_Parser classifierXMLParser = new SolutionXML_Parser();
//    }
//    
//    public SolutionXML_Parser() {
//        try {
//            Solution cs = parse(new File("D:/Desktop/temp.xml"));
//            SolutionXMLWriter writer = new SolutionXMLWriter();
//            String xmlString = writer.getXMLString(cs);
//            FileWriter fw = new FileWriter(new File("D:\\Desktop\\testLoopBack.xml"));
//            fw.write(xmlString);
//            fw.close();
//        } catch (ParserConfigurationException ex) {
//            Logger.getLogger(SolutionXML_Parser.class.getName()).log(Level.SEVERE, null, ex);
//        } catch (SAXException ex) {
//            Logger.getLogger(SolutionXML_Parser.class.getName()).log(Level.SEVERE, null, ex);
//        } catch (IOException ex) {
//            Logger.getLogger(SolutionXML_Parser.class.getName()).log(Level.SEVERE, null, ex);
//        }
//    }
//    
    public Solution parse(String s) throws ParserConfigurationException, SAXException, IOException {
        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance( );
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        InputStream is = new ByteArrayInputStream(s.getBytes());
        Document doc = docBuilder.parse(is);
        return parse(doc);
    }

    private Solution parse(Document doc) throws ParserConfigurationException, SAXException, IOException {
        doc.getDocumentElement().normalize();
        solution = null;
        classifierClassTemp = null;
        annotationSetTemp = null;
        parsedoc(doc);
        return solution;
    }

    private void parsedoc(Node n) {
        for (n = n.getFirstChild(); n != null; n = n.getNextSibling()) {
            if ("Solution".equalsIgnoreCase(n.getNodeName())) {
                NamedNodeMap a = n.getAttributes();
                String solutionName = NamedNodeMapFunc.getString(a, "Name");
                solution = new Solution(solutionName);
                int tileDim = NamedNodeMapFunc.getInteger(a, "TileDim");
                if (tileDim == 0){
                    solution.tileDim = 128;
                } else {
                    solution.tileDim = tileDim;
                }
                String description = NamedNodeMapFunc.getString(a, "Description");
                if (description != null){
                    solution.description = description;
                }
                parseSolution(n);
                break;
            }
        }
    }

    private void parseSolution(Node n) {
        for (n = n.getFirstChild(); n != null; n = n.getNextSibling()) {
            if ("ClassifierClass".equalsIgnoreCase(n.getNodeName())) {
                if (classifierClassTemp != null) {
                    solution.add(classifierClassTemp);
                }
                classifierClassTemp = new ClassifierClass();
                NamedNodeMap a = n.getAttributes();
                classifierClassTemp.setClassName(NamedNodeMapFunc.getString(a, "Name"));
                parseAnnotations(n);
            }
            if ("ClassifierSVM".equalsIgnoreCase(n.getNodeName())) {
                AbstractClassifier classifier = parseClassifierSVM(n);
                solution.setClassifier(classifier);
            }
            if ("FeatureGenerator".equalsIgnoreCase(n.getNodeName())) {
                AbstractFeatureGenerator featureGenerator = parseFeatureGenerator(n);
                solution.setFeatureGenerator(featureGenerator);
            }
        }
        if (classifierClassTemp != null) {
            solution.add(classifierClassTemp);
            classifierClassTemp = null;
        }
    }

    private ClassifierSVM parseClassifierSVM(Node n) {
        ClassifierSVM classifierSVM = new ClassifierSVM(ClassifierSVM.defaultParams(), true);
        svm_parameter p = classifierSVM.param;
        for (n = n.getFirstChild(); n != null; n = n.getNextSibling()) {
            if ("Model".equalsIgnoreCase(n.getNodeName())) {
                classifierSVM.setSvmModel(n.getTextContent());
            }
            if ("Zscore".equalsIgnoreCase(n.getNodeName())) {
                classifierSVM.setZscore(n.getTextContent());
            }
            if ("Param".equalsIgnoreCase(n.getNodeName())) {
                NamedNodeMap a = n.getAttributes();
                p.svm_type = NamedNodeMapFunc.getInteger(a, "svm_type");
                p.kernel_type = NamedNodeMapFunc.getInteger(a, "kernel_type");
                p.degree = NamedNodeMapFunc.getInteger(a, "degree");
                p.gamma = NamedNodeMapFunc.getDouble(a, "gamma");
                p.coef0 = NamedNodeMapFunc.getDouble(a, "coef0");
                p.nu = NamedNodeMapFunc.getDouble(a, "nu");
                p.cache_size = NamedNodeMapFunc.getDouble(a, "cache_size");
                p.C = NamedNodeMapFunc.getDouble(a, "C");
                p.eps = NamedNodeMapFunc.getDouble(a, "eps");
                p.p = NamedNodeMapFunc.getDouble(a, "p");
                p.shrinking = NamedNodeMapFunc.getInteger(a, "shrinking");
                p.probability = NamedNodeMapFunc.getInteger(a, "probability");
                p.nr_weight = NamedNodeMapFunc.getInteger(a, "nr_weight");
                classifierSVM.autoGamma = NamedNodeMapFunc.getBoolean(a, "autoGamma");
            }
            if ("ClassNames".equalsIgnoreCase(n.getNodeName())) {
                NamedNodeMap a = n.getAttributes();
                int numelNames = a.getLength();
                String names[] = new String[numelNames];
                for (int i = 0; i < numelNames; i++) {
                    Node item = a.item(i);
                    int idx = Integer.valueOf(item.getNodeName().substring(1));
                    if (idx < numelNames) {
                        names[idx] = item.getNodeValue();
                    }
                }
                classifierSVM.classNames = names;
            }
        }
        return classifierSVM;
    }

    private AbstractFeatureGenerator parseFeatureGenerator(Node n) {
        try {
            NamedNodeMap a = n.getAttributes();
            String generatorName = NamedNodeMapFunc.getString(a, "generatorName");
            Class<?> c = Class.forName(generatorName);
            Constructor constructor = c.getConstructor(new Class<?>[]{});
            AbstractFeatureGenerator featureGenerator = (AbstractFeatureGenerator) constructor.newInstance(new Object[]{});
            String featureNames = NamedNodeMapFunc.getString(a, "featureNames");
            featureGenerator.setFeatureNames(featureNames.split(","));
            return featureGenerator;
        } catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | InstantiationException ex) {
            Logger.getLogger(SolutionXML_Parser.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    private void parseAnnotations(Node n) {
        for (n = n.getFirstChild(); n != null; n = n.getNextSibling()) {
            if ("Annotations".equalsIgnoreCase(n.getNodeName())) {
                if (annotationSetTemp != null) {
                    classifierClassTemp.add(annotationSetTemp);
                }
                NamedNodeMap a = n.getAttributes();
                String slideReferenceXML = NamedNodeMapFunc.getString(a,"SlideReference");
                SlideReference slideReference = parseSlide(n);
                annotationSetTemp = new AnnotationSet(slideReference);
                parseAnnotation(n);
            }
        }
        if (annotationSetTemp != null) {
            classifierClassTemp.add(annotationSetTemp);
            annotationSetTemp = null;
        }
    }
    
    private SlideReference parseSlide(Node n) {
        for (n = n.getFirstChild(); n != null; n = n.getNextSibling()) {
            if ("slide".equalsIgnoreCase(n.getNodeName())) {
                NamedNodeMap a = n.getAttributes();
                String slideName = NamedNodeMapFunc.getString(a,"slideName");
                String slideSetName = NamedNodeMapFunc.getString(a,"slideSetName");
                String hashBase64 = NamedNodeMapFunc.getString(a,"hash");
                byte hash[] = DatatypeConverter.parseBase64Binary(hashBase64);
                SlideReference slideReference = new SlideReference(slideSetName, slideName, hash);
                return slideReference;
            }
        }
        throw new Error("Failed to parse slide reference in annotation set");
    }
    
    
    private void parseAnnotation(Node n) {
        for (n = n.getFirstChild(); n != null; n = n.getNextSibling()) {
            if ("Annotation".equalsIgnoreCase(n.getNodeName())) {
                AnnotationXML_Parser p = new AnnotationXML_Parser();
                Annotation anno = p.parseAnnotationNode(n);
                annotationSetTemp.add(anno);
            }
        }
    }

}
